/*
 * File     : PointerReferenceParameter.java
 * Created  : 24 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.googlecode.dni.internal.DniTypes;
import com.googlecode.dni.internal.MemoryAccess;
import com.googlecode.dni.type.ObjectReference;
import com.googlecode.dni.type.Pointer;

/**
 * Encapsulates an object reference parameter where the type is a
 * {@link Pointer}.
 *
 * @see ObjectReference
 *
 * @author Matthew Wilson
 */
final class PointerReferenceParameter extends Parameter
{

    /**
     * Creates a new instance.
     */
    PointerReferenceParameter()
    {
        setNativeTypeAsPointer();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getJavaVarSlots()
    {
        return 1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    boolean needsScratchMemory()
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeScratchMemorySize( final MethodVisitor methodVisitor )
    {
        if ( getNativeType() == 'J' )
        {
            methodVisitor.visitIntInsn( Opcodes.BIPUSH, 8 );
        }
        else
        {
            assert getNativeType() == 'I';
            methodVisitor.visitInsn( Opcodes.ICONST_4 );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writePrepareParameter( final MethodVisitor methodVisitor )
    {
        writeGetScratchPointer64( methodVisitor );
        methodVisitor.visitVarInsn( Opcodes.ALOAD, getJavaVarIndex() );
        methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
                                       DniTypes.OBJECT_REFERENCE_TYPE,
                                       "getValue",
                                       "()Ljava/lang/Object;" );
        methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, DniTypes.POINTER_TYPE );
        methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                       DniTypes.POINTER_TYPE,
                                       "getAddress",
                                       "(L" + DniTypes.POINTER_TYPE + ";)J" );

        if ( getNativeType() == 'J' )
        {
            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           MemoryAccess.UNSAFE_ACCESS_TYPE,
                                           "putLong",
                                           "(JJ)V" );
        }
        else
        {
            assert getNativeType() == 'I';
            methodVisitor.visitInsn( Opcodes.I2L );
            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           MemoryAccess.UNSAFE_ACCESS_TYPE,
                                           "putInt",
                                           "(JI)V" );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetParameter( final MethodVisitor methodVisitor )
    {
        writeGetScratchPointer();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    boolean needsTidying()
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeTidyParameter( final MethodVisitor methodVisitor,
                             final boolean isExceptionHandlerBlock )
    {
        if ( isExceptionHandlerBlock )
        {
            // don't update in exception handling
            return;
        }

        methodVisitor.visitVarInsn( Opcodes.ALOAD, getJavaVarIndex() );

        writeGetScratchPointer64( methodVisitor );

        if ( getNativeType() == 'J' )
        {
            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           MemoryAccess.UNSAFE_ACCESS_TYPE,
                                           "getLong",
                                           "(J)J" );

            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           DniTypes.POINTER_TYPE,
                                           "fromAddress",
                                           "(J)L" + DniTypes.POINTER_TYPE + ";" );
        }
        else
        {
            assert getNativeType() == 'I';
            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           MemoryAccess.UNSAFE_ACCESS_TYPE,
                                           "getInt",
                                           "(J)I" );

            methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC,
                                           DniTypes.POINTER_TYPE,
                                           "fromAddress",
                                           "(I)L" + DniTypes.POINTER_TYPE + ";" );
        }

        methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL,
                                       DniTypes.OBJECT_REFERENCE_TYPE,
                                       "setValue",
                                       "(Ljava/lang/Object;)V" );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeReturnParameter( final MethodVisitor methodVisitor, final int returnVarIndex )
    {
        throw new UnsupportedOperationException();
    }

}
